package knowledge

import (
	"context"
	"fmt"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/sashabaranov/go-openai"

	"xagent/internal/observability"
)

// Config represents the configuration for the RAG engine
type Config struct {
	EmbeddingModel string
	VectorStore    string
	// Add other configuration fields as needed
}

// Document represents a knowledge document
type Document struct {
	ID        string
	Title     string
	Content   string
	Embedding []float32
	Metadata  map[string]interface{}
	CreatedAt time.Time
	UpdatedAt time.Time
}

// SearchMatch represents a search result with relevance score
type SearchMatch struct {
	Document Document
	Score    float32
}

// RAGEngine represents a retrieval-augmented generation engine
type RAGEngine struct {
	config        *Config
	documents     map[string]*Document
	mutex         sync.RWMutex
	embedder      *openai.Client
	indexStrategy IndexStrategy
	maxResults    int
	sources       map[string]Source
	updateTicker  *time.Ticker
	logger        *observability.Logger
}

// NewRAGEngine creates a new RAG engine with the given configuration
func NewRAGEngine(config *Config) *RAGEngine {
	if config == nil {
		config = &Config{
			EmbeddingModel: "default",
			VectorStore:    "inmemory",
		}
	}
	// TODO: Get API key from environment or configuration
	apiKey := os.Getenv("OPENAI_API_KEY")
	if apiKey == "" {
		return nil
	}

	// Initialize index strategy based on config
	var indexStrategy IndexStrategy
	switch config.VectorStore {
	case "inmemory":
		indexStrategy = NewFaissIndex()
	default:
		indexStrategy = NewFaissIndex()
	}

	return &RAGEngine{
		config:        config,
		documents:     make(map[string]*Document),
		embedder:      openai.NewClient(apiKey),
		indexStrategy: indexStrategy,
		maxResults:    10,
		sources:       make(map[string]Source),
		logger:        observability.NewLogger(context.Background()),
	}
}

// AddDocument adds a document to the knowledge base
func (e *RAGEngine) AddDocument(ctx context.Context, doc *Document) error {
	e.mutex.Lock()
	defer e.mutex.Unlock()

	if doc.ID == "" {
		return fmt.Errorf("document ID cannot be empty")
	}

	// Generate embedding if not exists
	if len(doc.Embedding) == 0 {
		resp, err := e.embedder.CreateEmbeddings(context.Background(), openai.EmbeddingRequest{
			Input: []string{doc.Content},
			Model: openai.AdaEmbeddingV2,
		})
		if err != nil {
			return err
		}
		if len(resp.Data) > 0 {
			doc.Embedding = resp.Data[0].Embedding
		}
	}

	doc.UpdatedAt = time.Now()
	e.documents[doc.ID] = doc
	return e.indexStrategy.Index(doc)
}

// GetDocument retrieves a document by ID
func (e *RAGEngine) GetDocument(ctx context.Context, id string) (*Document, error) {
	e.mutex.RLock()
	defer e.mutex.RUnlock()

	doc, ok := e.documents[id]
	if !ok {
		return nil, fmt.Errorf("document not found: %s", id)
	}
	return doc, nil
}

// AddSource adds a knowledge source
func (r *RAGEngine) AddSource(source Source) error {
	r.sources[source.ID()] = source
	return source.Update(context.Background())
}

// StartSourceUpdates starts periodic source updates
func (r *RAGEngine) StartSourceUpdates(ctx context.Context, interval time.Duration) {
	r.updateTicker = time.NewTicker(interval)
	go func() {
		for {
			select {
			case <-ctx.Done():
				r.updateTicker.Stop()
				return
			case <-r.updateTicker.C:
				r.updateSources(ctx)
			}
		}
	}()
}

// updateSources updates all knowledge sources
func (r *RAGEngine) updateSources(ctx context.Context) {
	for _, source := range r.sources {
		if err := source.Update(ctx); err != nil {
			// Log error but continue with other sources
			continue
		}
	}
}

// Search performs a semantic search over the knowledge base
func (e *RAGEngine) Search(ctx context.Context, query string, limit int) ([]SearchMatch, error) {
	e.mutex.RLock()
	defer e.mutex.RUnlock()

	// TODO: Implement semantic search using embeddings
	// For now, return a simple substring match
	var results []SearchMatch
	for _, doc := range e.documents {
		if len(results) >= limit {
			break
		}
		// Simple substring match for demonstration
		if containsIgnoreCase(doc.Content, query) {
			results = append(results, SearchMatch{
				Document: *doc,
				Score:    1.0, // Placeholder score
			})
		}
	}
	return results, nil
}

// DeleteDocument removes a document from the knowledge base
func (e *RAGEngine) DeleteDocument(ctx context.Context, id string) error {
	e.mutex.Lock()
	defer e.mutex.Unlock()

	if _, ok := e.documents[id]; !ok {
		return fmt.Errorf("document not found: %s", id)
	}

	delete(e.documents, id)
	return nil
}

// UpdateDocument updates an existing document
func (e *RAGEngine) UpdateDocument(ctx context.Context, doc *Document) error {
	e.mutex.Lock()
	defer e.mutex.Unlock()

	if _, ok := e.documents[doc.ID]; !ok {
		return fmt.Errorf("document not found: %s", doc.ID)
	}

	// TODO: Update embeddings if content changed
	doc.UpdatedAt = time.Now()
	e.documents[doc.ID] = doc
	return nil
}

// Helper functions

func containsIgnoreCase(s, substr string) bool {
	s, substr = strings.ToLower(s), strings.ToLower(substr)
	return strings.Contains(s, substr)
}
